

#include "JvmDllLoader.h"
using namespace Pan;

#include <windows.h>
#include <stdio.h>
#include <io.h>

// 规定字符数组的长度
#ifndef MAX_STR
#define MAX_STR (1024)
#endif

JvmDll::JvmDll(void *handle) : _handle(handle)
{
}

JvmDll::JvmDll(JvmDll &&jvm_dll)
{
	this->_handle = jvm_dll._handle;

	// 使临时对象不在构造函数中释放内存
	jvm_dll._handle = nullptr;
}

void JvmDll::operator=(JvmDll &&jvm_dll)
{
	this->_handle = jvm_dll._handle;

	// 使临时对象不在构造函数中释放内存
	jvm_dll._handle = nullptr;
}

JvmDll::~JvmDll()
{
	// ::prinstf("JvmDll::~JvmDll(): %x\n", this->_handle);

	if (this->_handle != nullptr)
	{
		::FreeLibrary(reinterpret_cast<HMODULE>(this->_handle));
		this->_handle = nullptr;
	}
}

bool JvmDll::is_valid()
{
	return this->_handle != nullptr;
}

/**********************************************************************/

JvmDll JvmDllLoader::loadJvmDll()
{
	JvmDll jvm_dll(nullptr);

	if (!jvm_dll.is_valid())
	{
		jvm_dll = JvmDllLoader::loadJvmDll_nearFolder();
	}

	if (!jvm_dll.is_valid())
	{
		jvm_dll = JvmDllLoader::loadJvmDll_pathEnv();
	}

	if (!jvm_dll.is_valid())
	{
		jvm_dll = JvmDllLoader::loadJvmDll_javaInstall();
	}

	return jvm_dll;
}

JvmDllLoader::JvmDllLoader()
{
}

JvmDllLoader::~JvmDllLoader()
{
}

bool JvmDllLoader::checkFileExists(const wchar_t *filePath)
{
	// 空指针检查
	if (!filePath)
		return false;

	// 尝试打开文件
	HANDLE handle(NULL);
	handle = ::CreateFileW(
		filePath,
		0,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

	if (handle && handle != INVALID_HANDLE_VALUE)
	{
		::CloseHandle(handle);
		return true;
	}
	else
	{
		return false;
	}
}

void JvmDllLoader::changeToParentPath(wchar_t *fullPath)
{
	if (!fullPath)
		return;

	unsigned int i(0);
	while (fullPath[i])
		i++;
	while (fullPath[i] != '\\' && i > 0)
		i--;
	if (fullPath[i] == '\\' && i >= 1)
	{
		if (fullPath[i - 1] == ':')
		{
			fullPath[i + 1] = 0;
		}
		else
		{
			fullPath[i] = 0;
		}
	}
}

JvmDll JvmDllLoader::tryLoadWithBinaryDirectory(const wchar_t *directory)
{
	wchar_t *fullPath = new wchar_t[MAX_STR];
	HMODULE module(NULL);

	DWORD len(0);
	len = ::GetFullPathNameW(directory, MAX_STR, fullPath, NULL);
	if (len > 0)
	{
		// 看看最后一个字符是不是"\\"
		if (fullPath[len - 1] != '\\')
		{
			fullPath[len] = '\\';
			len++;
		}

		// 检查java.exe
		JvmDllLoader::stringCopy(&fullPath[len], L"java.exe");
		if (!JvmDllLoader::checkFileExists(fullPath))
		{
			goto out;
		}

		// 检查javaw.exe
		JvmDllLoader::stringCopy(&fullPath[len], L"javaw.exe");
		if (!JvmDllLoader::checkFileExists(fullPath))
		{
			goto out;
		}

		// "C:\Program Files\Java\jdk-11.0.1\bin\server\jvm.dll"
		// "C:\Program Files (x86)\Java\jre1.8.0_192\bin\client\jvm.dll"
		// "C:\Program Files (x86)\Java\jdk1.8.0_192\jre\bin\server\jvm.dll"
		// "C:\Program Files\Java\jdk1.8.0_192\jre\bin\server\jvm.dll"
		// "C:\Program Files\Java\jre1.8.0_192\bin\server\jvm.dll"

		JvmDllLoader::stringCopy(&fullPath[len], L"server\\jvm.dll");
		module = ::LoadLibraryW(fullPath);
		if (module)
			goto out;

		JvmDllLoader::stringCopy(&fullPath[len], L"client\\jvm.dll");
		module = ::LoadLibraryW(fullPath);
		if (module)
			goto out;
	}

out:
	delete[] fullPath;
	fullPath = nullptr;

	return JvmDll(module);
}

JvmDll JvmDllLoader::tryLoadWithJavaHome(const wchar_t *javaHome)
{
	JvmDll jvm_dll;

	// 空指针检查
	if (!javaHome)
		return jvm_dll;

	// 保存绝对路径
	wchar_t *fullPath = new wchar_t[MAX_STR];

	HMODULE hmodule(NULL);

	//转换为绝对路径
	DWORD len(0);
	len = ::GetFullPathNameW(javaHome, MAX_STR, fullPath, nullptr);
	if (len > 0)
	{
		// 如果最后一个字符不是"\\"
		if (fullPath[len - 1] != '\\')
		{
			fullPath[len] = '\\';
			len++;
		}

		// 检查"bin\\java.exe"
		JvmDllLoader::stringCopy(&fullPath[len], L"bin\\java.exe");
		if (!JvmDllLoader::checkFileExists(fullPath))
			goto out;

		// 检查"bin\\javaw.exe"
		JvmDllLoader::stringCopy(&fullPath[len], L"bin\\javaw.exe");
		if (!JvmDllLoader::checkFileExists(fullPath))
			goto out;

		JvmDllLoader::stringCopy(&fullPath[len], L"jre\\bin\\server\\jvm.dll");
		hmodule = ::LoadLibraryW(fullPath);
		if (hmodule)
			goto out;

		JvmDllLoader::stringCopy(&fullPath[len], L"jre\\bin\\client\\jvm.dll");
		hmodule = ::LoadLibraryW(fullPath);
		if (hmodule)
			goto out;

		JvmDllLoader::stringCopy(&fullPath[len], L"bin\\server\\jvm.dll");
		hmodule = ::LoadLibraryW(fullPath);
		if (hmodule)
			goto out;

		JvmDllLoader::stringCopy(&fullPath[len], L"bin\\client\\jvm.dll");
		hmodule = ::LoadLibraryW(fullPath);
		if (hmodule)
			goto out;
	}

out:
	delete[] fullPath;
	fullPath = nullptr;
	return JvmDll(hmodule);
}

JvmDll JvmDllLoader::tryLoadWithDirectoryHasJavaHome(const wchar_t *directory)
{
	JvmDll jvm_dll;

	// 空指针检查
	if (!directory)
		return jvm_dll;

	HANDLE find(NULL); // 查找句柄
	wchar_t *buffer = new wchar_t[MAX_STR];
	if (!buffer)
		return NULL;
	WIN32_FIND_DATAW data;

	// 转换为绝对路径
	DWORD len = ::GetFullPathNameW(directory, MAX_STR, buffer, nullptr);
	if (len <= 0)
		goto out; // 转换失败

	// %dir%\\*
	{
		// 如果最后一个不是"\\"
		if (buffer[len - 1] != '\\')
		{
			buffer[len] = '\\';
			len++;
		}
		buffer[len] = '*';
		len++;
		buffer[len] = 0;
	}

	::RtlSecureZeroMemory(&data, sizeof(data));
	find = ::FindFirstFileW(buffer, &data);
	if (find && find != INVALID_HANDLE_VALUE)
	{
		do
		{
			const wchar_t *name(data.cFileName);
			if ((name[0] == '.' && name[1] == 0) ||
				(name[0] == '.' && name[1] == '.' && name[2] == 0))
			{
				// 忽略"."和".."
			}
			else
			{
				// 只处理文件夹
				if (data.dwFileAttributes != INVALID_FILE_ATTRIBUTES && (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
				{
					wchar_t *p(buffer);
					while (*p)
						p++;
					while (*p != '\\' && p != buffer)
						p--;
					if (*p == '\\')
					{
						p++;
						JvmDllLoader::stringCopy(p, data.cFileName);
					}
					jvm_dll = JvmDllLoader::tryLoadWithJavaHome(buffer);
					if (jvm_dll.is_valid())
						break;
				}
			}

		} while (::FindNextFileW(find, &data));

		::FindClose(find);
	}
out:
	delete[] buffer;
	buffer = nullptr;
	return jvm_dll;
}

void JvmDllLoader::stringCopy(wchar_t *to, const wchar_t *from)
{
	unsigned int i = 0;
	while (from[i] != '\0')
	{
		to[i] = from[i];
		i++;
	}
	to[i] = '\0';
}

JvmDll JvmDllLoader::loadJvmDll_pathEnv()
{
	JvmDll jvm_dll(nullptr);

	wchar_t *pathEnv = new wchar_t[MAX_STR];

	// 非常必要的清零
	::RtlSecureZeroMemory(pathEnv, MAX_STR * sizeof(wchar_t));

	DWORD len(0);
	len = ::GetEnvironmentVariableW(L"path", pathEnv, MAX_STR);
	if (len > 0) // 获取环境变量成功
	{
		// 把所有的分号(";")换成字符串结束符0
		for (unsigned int i = 0; i < len; i++)
		{
			if (pathEnv[i] == ';')
			{
				pathEnv[i] = 0;
			}
		}

		// 遍历path的每个路径
		wchar_t *eachPath(pathEnv);
		do
		{
			// 操作
			jvm_dll = JvmDllLoader::tryLoadWithBinaryDirectory(eachPath);
			if (jvm_dll.is_valid())
				break;

			// 把eachPath移动到这个字符串的结尾
			while (*eachPath)
				eachPath++;

			// 把eachPath移动到下一个字符串的开头
			unsigned int i = 0;
			while (*eachPath == 0 && i <= 4)
			{
				eachPath++;
				i++;
			}

		} while (*eachPath != 0);
	}

	delete[] pathEnv;
	pathEnv = nullptr;

	return jvm_dll;
}

JvmDll JvmDllLoader::loadJvmDll_nearFolder()
{

	JvmDll jvm_dll;
	wchar_t *workingDirectory = new wchar_t[MAX_STR];

	// 获取当前目录所在的文件夹
	DWORD len(0);
	len = ::GetCurrentDirectoryW(MAX_STR, workingDirectory);

	// 获取失败
	if (len <= 0)
		goto out;

	// 如果最后一个字符是"\\", 那么去掉
	if (workingDirectory[len - 1] == '\\')
	{
		len -= 1;
		workingDirectory[len] = 0;
	}

	// 尝试扫描当前目录加载
	if (!jvm_dll.is_valid())
	{
		jvm_dll = JvmDllLoader::tryLoadWithDirectoryHasJavaHome(workingDirectory);
	}

	// 尝试扫描当前目录的上级目录加载
	if (!jvm_dll.is_valid())
	{
		JvmDllLoader::changeToParentPath(workingDirectory);
		jvm_dll = JvmDllLoader::tryLoadWithDirectoryHasJavaHome(workingDirectory);
	}

out:
	delete[] workingDirectory;
	workingDirectory = nullptr;
	return jvm_dll;
}

JvmDll JvmDllLoader::loadJvmDll_javaInstall()
{
	JvmDll jvm_dll; // 返回值

	wchar_t *buffer = new wchar_t[MAX_STR];
	DWORD len(0);

	// "ProgramFiles"
	if (!jvm_dll.is_valid())
	{
		len = ::GetEnvironmentVariableW(L"ProgramFiles", buffer, MAX_STR);
		if (len > 0) // 获取成功
		{
			JvmDllLoader::stringCopy(&buffer[len], L"\\Java");
			jvm_dll = JvmDllLoader::tryLoadWithDirectoryHasJavaHome(buffer);
		}
	}

	// "ProgramW6432"
	if (!jvm_dll.is_valid())
	{
		len = ::GetEnvironmentVariableW(L"ProgramW6432", buffer, MAX_STR);
		if (len > 0)
		{
			JvmDllLoader::stringCopy(&buffer[len], L"\\Java");
			jvm_dll = JvmDllLoader::tryLoadWithDirectoryHasJavaHome(buffer);
		}
	}

	// "ProgramFiles(x86)"
	if (!jvm_dll.is_valid())
	{
		len = ::GetEnvironmentVariableW(L"ProgramFiles(x86)", buffer, MAX_STR);
		if (len > 0)
		{
			JvmDllLoader::stringCopy(&buffer[len], L"\\Java");
			jvm_dll = JvmDllLoader::tryLoadWithDirectoryHasJavaHome(buffer);
		}
	}

	delete[] buffer, buffer = NULL;

	return jvm_dll;
}
